package mkv.MyGUI;
import processing.core.*;
import java.awt.event.KeyEvent;

/**
 * Visual GUI element for receiving and storing typed characters.
 * A MyGUITextInput object allows a user to enter in text to a program in a visual manner
 * within a confined space, defined by the _width and _height parameters.
 * The default _width is set to 100 pixels whilst the _height will vary depending on the 
 * font size defined in the MyGUIStyle inherited by this object. 
 * <p>
 * Bear in mind that this is a cut down text field object compared to what most users
 * are used to. It currently supports all ASCII characters, Backspace, Delete, Home, End,
 * Left and Right keys which all perform to the normal standards. It also supports clicking
 * to place the key cursor inbetween two characters. Highlighting multiple letters does NOT
 * work, and neither do the Copy, Cut or Paste commands.
 * <p>
 * Pressing ENTER while this object has focus will generate a MyGUIActionEvent in the
 * root processing applet.
 * <p>
 * MyGUITextInput objects inherit all the basic properties and methods from MyGUIObject,
 * The _x and _y coordinates specify the center of the text input box, and act as the rotation
 * and scale point when the object is drawn to the screen.
 * <p>
 * The text input uses all its colours from MyGUIStyle. In particular it uses the
 * MyGUIStyle.scrollBar colour as the light coloured background on which text is drawn.
 * The text colour is MyGUIStyle.buttonText. Please ensure that these two colours have
 * suitable contrast when customizing the colours.
 * 
 * @author John Beech
 */
public class MyGUITextInput extends MyGUIObject {
    
    /**
     * Full text data stored by the text input
     */
    public String _value;	// This is the actual value of the text input
    /**
     * The visible portion of text to be rendered. Always derived from _value.
     */
    public String _text;  // This is the displayed, visual value based on _value;
    private boolean fixedSize, cursorVisible, keyPressed;
    private int cursorLetterPos, textOffsetPos, cursorPixelPos, cursorFlash;

    /**
     * Most basic constructor, creates a default sized text area on the screen
     * @param root 		The PApplet parent where this item will be drawn
     * @param x the 	x coordinate of the text input
     * @param y the 	y coordinate of the text input
     */
    public MyGUITextInput(PApplet root, int x, int y) {
        super(root, x, y);
        _width = 100;
        fixedSize = false; // only applies to height
        _text = "";
        _value = "";
        cursorLetterPos = 0;
        textOffsetPos = 0;
    }
    
    /**
     * This version lets you specify the width and height of the text box directly.
     * @param root 		The PApplet parent where this item will be drawn
     * @param x				x coordinate of the text input
     * @param y				y coordinate of the text input
     * @param width		width of the text input
     * @param height	height of the text input
     */
    public MyGUITextInput(PApplet root, int x, int y, int width, int height) {
        super(root, x, y);
        _width = width;
        _height = height;
        fixedSize = true;
        _text = "";
        _value = "";
        cursorLetterPos = 0;
        textOffsetPos = 0;
    }
    /**
     * Sets the text String value of the text input. 
     * @param text the new text to be stored in the text field. 
     * @see #_text
     */
    public void setValue(String text) {
        _value = text;
        updateText();
    }
    
    /**
     * Gets the text String value of the text input.
     * 
     * @return returns the String stored by the text input. 
     * 
     * @see #_text
     */
    public String getValue() {
        return _value;
    }
  
    /**
     * Called when a mouse is pressed on top of the object giving it focus. Enables the
     * dragged state and displays the cursor next to the nearest letter in the text field
     * by calling updateText().
     */
    public void mousePressed() {
        if(hasFocus()) {
          dragged = true;
          cursorVisible = true; // text input gains focus so display cursor immediately
          cursorFlash = 1; // reset time it takes for cursor to flash
          updateText(); // update the way the texfield is drawn
        }
    }
    
    /**
     * Updates the gubbins and values that make the text input work. It loads font metrics
     * to position the cursor inbetween letters and has values to track the text offset 
     * as well as the cursor letter position. These values are then used when typing
     * letters so that they are inserted in the right places.
     *
     */
    private void updateText() {
        // Font metrics are required heavily
        MyGUIStyle st = _style;
        if(_style == null) {
            st = _parent.getStyle();
        } else {
            st = _style;
        }
        _root.textFont(st.font, st.fontSize);
        // Check width of text
        int textWidth = (int)_root.textWidth(_value);
        // Ensure cursor letter postion is within boundaries
        cursorLetterPos = PApplet.constrain(cursorLetterPos, 0, _value.length());
        // Ensure cursorLetter position is never below the textOffset position - by reducing the textOffset
		    if(cursorLetterPos < textOffsetPos) {
		        textOffsetPos = cursorLetterPos;
		    }
        if(textWidth <= _width) {
            // If text fits comfortably inside text input, then set
            // textOffset to 0 so that all the letters can be seen
            textOffsetPos = 0;
        } else
        if(cursorLetterPos > textOffsetPos) {
            // Offset things so cursor appears at end of box
            String toposText = _value.substring(textOffsetPos, cursorLetterPos); 
            while(_root.textWidth(toposText) > _width) {
                // Reduce textOffsetPos until there is enough text
                // to fill the width of the text box.
                textOffsetPos++;
                if(cursorLetterPos < _value.length()) {
                    toposText = _value.substring(textOffsetPos, cursorLetterPos);
                } else {
                    toposText = _value.substring(textOffsetPos);
                }
            }
        }
        if(textWidth > _width) {
            _text = _value.substring(textOffsetPos);
            if(PApplet.floor(_root.textWidth(_text)) < _width) {
                do {
                    // extend text until it just overflows
                    textOffsetPos--;
                    _text = _value.substring(textOffsetPos);   
                } while(PApplet.ceil(_root.textWidth(_text)) < _width);
                // This loop by nature always overuns, so needs to be reduced
                textOffsetPos++;
                _text = _value.substring(textOffsetPos);
            } else {
                if(cursorLetterPos >= _value.length()) {
                    while(PApplet.floor(_root.textWidth(_text)) >= _width-st.padding-1) {
                        // shorten text from start until it fits.
                        _text = _text.substring(1);
                        textOffsetPos++;
                    }
                } else {
                    while(PApplet.floor(_root.textWidth(_text)) >= _width-st.padding-1) {
                        // shorten text from end until it fits.
                        _text = _text.substring(0, _text.length()-1);
                    }
                }
            }
        } else {
            _text = _value.substring(textOffsetPos);
        }
        // Place cursor if mouse dragged
        String cposText = _text+"";
        if(dragged) {
            cursorPixelPos = PApplet.round((float)(tmouseX)+_width/2);
            cursorLetterPos = textOffsetPos + cposText.length();
            // Loop special cposText variable to find closest letter to mouse click
            while(PApplet.floor(_root.textWidth(cposText)) > cursorPixelPos) {
                cursorLetterPos--;
                cposText = cposText.substring(0, cposText.length()-1);
            }
        } else
        if(cursorLetterPos > 0) {
            // Set special cposText variable to position cursor based on int's
            cposText = _value.substring(textOffsetPos, cursorLetterPos);
        } else {
            cposText = "";
        }
        // Work out position of cursor to nearest letter
        cursorPixelPos = PApplet.floor(_root.textWidth(cposText));
        // Ensure cusor position lies within boundaries
        cursorPixelPos = PApplet.constrain(cursorPixelPos, 0, _width-st.padding-1);
    }
    
    /**
     * Detects when a non ascii key code is pressed. This method currently processes
     * The HOME and END keys moving the cursor to the start/end of the text, and also
     * the LEFT and RIGHT keys allowing a user to scroll back and forth inside the text
     * input. It displays the cursor while these buttons are pressed and calls updateText()
     * to update the visible text and the cursor position.
     * 
     * @see KeyEvent#getKeyCode()
     */
    public void keyPressed(KeyEvent e) {
        int kc = e.getKeyCode();
        switch(kc) {
      			case KeyEvent.VK_HOME:
      			    textOffsetPos = 0;
      			    cursorLetterPos = 0;
      			    break;
      			case KeyEvent.VK_END:
      			    cursorLetterPos = _value.length();
      			    textOffsetPos = _value.length();
      			    break;
      			case KeyEvent.VK_LEFT:
      			    cursorLetterPos--;
      			    break;
      			case KeyEvent.VK_RIGHT:
      			    cursorLetterPos++;
      			    break;
        }
        keyPressed = true;
        cursorVisible = true; // display cursor immediately
        cursorFlash = 1; // reset time it takes for cursor to flash
        updateText();
    }  
    
    /**
     * Detects when an ascii key is typed on the keyboard. These characters are then added
     * to the string value stored by the text input. This method also supports BACK SPACE
     * removing a character before the cursor and DELETE removing the character after the 
     * cursor. It also handles the ENTER key by creating an ActionEvent in the _root
     * applet. Any other key is added to the string just after the cursor.
     * @see KeyEvent#getKeyChar()
     * @see #_value
     */
    public void keyTyped(KeyEvent e) {
        int kc = e.getKeyChar();
        switch(kc) {
        case KeyEvent.VK_BACK_SPACE:
            if(_value.length() > 0) {
                if(cursorLetterPos < _value.length()) {
                    if(cursorLetterPos > 0) {
                        String temp = _value+"";
                        _value = (temp.substring(0, cursorLetterPos-1) + temp.substring(cursorLetterPos));
                    }
                } else {
                    int len = _value.length();
                    _value = _value.substring(0, len-1);
                }
                if(cursorLetterPos > 0) { cursorLetterPos--; }
            }
            break;
        case KeyEvent.VK_DELETE:
            if(_value.length() > 0) {
                if(cursorLetterPos < _value.length()) {
                    if(cursorLetterPos > 0) {
                        String temp = _value+"";
                        _value = (temp.substring(0, cursorLetterPos) + temp.substring(cursorLetterPos+1));
                    } else {
                        _value = _value.substring(1);
                    }
                }
            }
            break;
        case KeyEvent.VK_ENTER:
            // Create ActionEvent when ENTER is pressed.
            MyGUIActionEvent a = new MyGUIActionEvent(this, _actionCommand);
            a.sendEvent(_root);
            break;
        default:
            String temp = _value+"";
            _value = temp.substring(0, cursorLetterPos) + e.getKeyChar() + temp.substring(cursorLetterPos);
        		cursorLetterPos++;
        break;
        }
        updateText();
    }
    
    /**
     * When a key is released the keyPressed value is set to false.
     * @see #keyPressed
     */
    public void keyReleased(KeyEvent e) {
        keyPressed = false;
    }
    
    /**
     * Draw methods for the object based on the object's various states. This method
     * draws the text input on to the screen, called automatically by the draw() method.
     * It first draws the background box based on state, then draws the cursor and finally
     * renders the text to the screen.
     * @see MyGUIStyle
     * @see MyGUIObject#isDisabled()
     * @see MyGUIObject#hover
     * @see MyGUIObject#hasFocus()
     * @see MyGUIObject#dragged
     */
    public void drawStates() {
        // alias to shorten _style property to st
        MyGUIStyle st = _style;
        if(_style == null) {
            st = _parent.getStyle();
        } else {
            st = _style;
        }
        // set width/height if not already
        if(!fixedSize) {
            _root.textFont(st.font, st.fontSize);
            _height = st.fontSize + 2 + st.padding;
            if(_height%2 == 1) _height++; // hack so that centrally drawn lines are always in the same place
        }
        // set regularly used vars:
        final int halfw = PApplet.round((float)_width/2 + st.padding);
        final int halfh = PApplet.round((float)_height/2 + st.padding);
        // set text offset (used each time text is drawn to screen)
        int textVOffset = st.fontSize/2 - st.padding/2;
        int textHOffset = 0;
        // decide on draw state
        hover = checkForHit();
        // move draw object to coordinates
        _root.pushMatrix();
        _root.translate(_x, _y);
        _root.scale(_scale);
        _root.rotate(PApplet.radians(_rotation));
        // Set default properties
        _root.rectMode(PApplet.CORNERS);
        _root.strokeWeight(st.strokeWeight);
        _root.textFont(st.font, st.fontSize);
        _root.textAlign(PApplet.LEFT);
        if(isDisabled()) {
            // disabled state  
            _root.stroke(st.shadow);
            _root.fill(st.disabled);
            _root.rect(-halfw, -halfh, halfw, halfh);
        } else if(hover) {
            // hover state
            _root.stroke(st.highlight);
            _root.fill(st.scrollBar);
            _root.rect(-halfw, -halfh, halfw, halfh);
        } else {
            // default state
            _root.stroke(st.highlight);
            _root.fill(st.scrollBar);
            _root.rect(-halfw, -halfh, halfw, halfh);
        }
        if(hasFocus()) {
            // Increment cursor for flash
            cursorFlash++;
            if(cursorFlash % 20 == 0) {
                cursorFlash = 1;
                cursorVisible = !cursorVisible;
            }
            // Draw cursor
            if(cursorVisible) {
                _root.stroke(st.shadow);
                _root.line(cursorPixelPos-halfw+st.padding+1, -halfh+2, cursorPixelPos-halfw+st.padding+1, halfh-2);
            }
        }
        // Draw text
        _root.fill(st.buttonText);
        _root.text(_text, -halfw+st.padding+1, -textVOffset, _width, _height);
        _root.popMatrix();
    }
}
